/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package compilador.analisadorSemantico;

import compilador.analisadorLexico.Token;
import compilador.analisadorSintatico.simboloGramatical.NaoTerminal;
import compilador.analisadorSintatico.simboloGramatical.SimboloGramatical;
import compilador.analisadorSintatico.simboloGramatical.Terminal;
import compilador.tabelaDeSimbolos.InsertException;
import compilador.tabelaDeSimbolos.TabelaSimbolos;
import java.util.List;

/**
 *
 * @author Henrique
 */
public class GeradorTabela {
    public static final boolean DEBUG = AnalisadorSemantico.DEBUG;

    private AnalisadorSemantico analisadorSemantico;
    
    public GeradorTabela(AnalisadorSemantico analisadorSemantico) {
        this.analisadorSemantico = analisadorSemantico;
    }
    
    public void preencheTabelaSimbolos(){
        TabelaSimbolos.createInstance();
        SimboloGramatical classe = analisadorSemantico.getArvoreSintatica().getRaiz();

        procuraNaoTerminal(classe, (NaoTerminal) classe);

        TabelaSimbolos.print();
    }

    /**
     * Procura DEC_VARIAVEL, DEC_CONSTANTE, DEC_METODO, DEC_CLASSE_INTERNA, PARAM
     * @param sg
     */
    private void procuraNaoTerminal(SimboloGramatical sg, NaoTerminal escopo){
        List<SimboloGramatical> filhos;


        if(sg instanceof NaoTerminal){
            NaoTerminal nt = (NaoTerminal) sg;

            escopo = AnalisadorSemantico.procuraEscopo(nt, escopo);
            procuraDeclaracao(nt, escopo);

            //procura novos filhos
            filhos = nt.getFilhos();

            for(SimboloGramatical sgFilho: filhos){
                procuraNaoTerminal(sgFilho, escopo);

            }

        }
    }

    /**
     * Procura uma declaração e insere os simbolos na tabela
     * caso o tipo de simbolo seja array, na tabela é acrescentado '[]' ao nome do tipo
     *
     * Ex. int[] casas - na tabela o nome do tipo é igual à: 'int[]'
     *
     * @param nt - Terminal a ser analisado
     * @param escopo - Escopo atual
     * @return
     */
    private void procuraDeclaracao(NaoTerminal nt, NaoTerminal escopo){

        String tipo;
        String nome;
        Token idToken;

        int linha = AnalisadorSemantico.getNumeroLinha(nt);

        //<DEC_VARIAVEL> ::= <TIPO> [ ‘[‘ ‘]’  ] <NOME> [ <ATRIBUIR_VALOR> | { ‘,’ <NOME> } ] ‘;’
        if (nt.getNome().equals("DEC_VARIAVEL")) {
            NaoTerminal tipoNTerminal = (NaoTerminal) nt.get(0); //Não Terminal Tipo
            tipo = AnalisadorSemantico.extraiIdDeTipo(tipoNTerminal);

            NaoTerminal nomeNTerminal = null;

            int indexFilho = 2;
            if(nt.get(1) instanceof Terminal){
                tipo = tipo + "[]";

                nomeNTerminal = (NaoTerminal) nt.get(3);
                indexFilho = 4;
            }else if(nt.get(1) instanceof NaoTerminal){
                nomeNTerminal = (NaoTerminal) nt.get(1);

                if(nt.get(2) instanceof Terminal){
                    Terminal t = (Terminal) nt.get(indexFilho);
                    if(t.getSimbolo().getAtributo().toString().equals("[")){
                        tipo = tipo + "[]";
                        indexFilho = 4;
                    }
                }
            }

            //insere na tabela de símbolos
            nome = AnalisadorSemantico.extraiIdDeNomeOuTipoPrimitivo(nomeNTerminal);
            insereSimbolo(nome, tipo, escopo, linha);


            //procura outras variáveis qd ocorrer: tipo id1, id2;
            while((indexFilho + 1) < nt.getFilhos().size() && nt.get(indexFilho) instanceof Terminal){
                Terminal t = (Terminal) nt.get(indexFilho);

                if(t.getSimbolo().getAtributo().toString().equals(",")){
                    nomeNTerminal = (NaoTerminal) nt.get(indexFilho + 1);

                    //insere na tabela de símbolos
                    nome = AnalisadorSemantico.extraiIdDeNomeOuTipoPrimitivo(nomeNTerminal);
                    insereSimbolo(nome, tipo, escopo, linha);

                    indexFilho += 2;
                }else{
                    break;
                }
            }
        }
        // <DEC_CONSTANTE> ::= const <TIPO_PRIMITIVO> <NOME> <ATRIBUIR_VALOR> â€˜;â€™
        else if(nt.getNome().equals("DEC_CONSTANTE")){
            NaoTerminal tipoNaoTerminal = (NaoTerminal) nt.get(1); //NÃ£o Terminal Tipo
            Terminal identificadorTerminal = (Terminal) tipoNaoTerminal.get(0);

            idToken = identificadorTerminal.getSimbolo();

            tipo = idToken.getAtributo().toString(); //id ou palavra reservada char, int
            NaoTerminal nomeNaoTerminal;

            nomeNaoTerminal = (NaoTerminal) nt.get(2);

            nome = AnalisadorSemantico.extraiIdDeNomeOuTipoPrimitivo(nomeNaoTerminal);

            //insere na tabela de sÃ­mbolos
            insereSimbolo(nome, tipo, escopo, idToken.getLinha());
        }
        // <DEC_CLASSE_INTERNA> ::= class <NOME>â€˜{â€˜ { <DEC_ATRIBUTO> } â€˜}â€™
        else if(nt.getNome().equals("DEC_CLASSE_INTERNA")){
            Terminal tipoTerminal = (Terminal) nt.get(0); //NÃ£o Terminal Tipo
            //Terminal identificadorTerminal = (Terminal) tipoNaoTerminal.get(0);

            idToken = tipoTerminal.getSimbolo();
            tipo = idToken.getAtributo().toString(); //id ou palavra reservada char, int

            NaoTerminal nomeNaoTerminal;

            nomeNaoTerminal = (NaoTerminal) nt.get(1);


            nome = AnalisadorSemantico.extraiIdDeNomeOuTipoPrimitivo(nomeNaoTerminal);

            //insere na tabela de sÃ­mbolos
            insereSimbolo(nome, tipo, escopo, idToken.getLinha());
        }
        // <DEC_METODO> ::= <TIPO_RETORNO> <NOME> â€˜(â€˜ [ <DEC_PARAMETROS> ] â€˜)â€™ â€˜{â€˜ <EXP_METODO>â€˜}â€™

        //TIPO_RETORNO
        else if(nt.getNome().equals("DEC_METODO")){
            NaoTerminal tipoRetornoNaoTerminal = (NaoTerminal) nt.get(0); //NÃ£o Terminal Tipo Retorno

            String tipoRetorno = "";
            if(tipoRetornoNaoTerminal.get(0) instanceof NaoTerminal){
               //TIPO
                NaoTerminal tipoNaoTerminal = (NaoTerminal) tipoRetornoNaoTerminal.get(0);
                tipoRetorno = AnalisadorSemantico.extraiIdDeTipo(tipoNaoTerminal);


            } else if (tipoRetornoNaoTerminal.get(0) instanceof Terminal){
                tipoRetorno = "void";

            }

            tipo = "METODO!" + tipoRetorno;

            NaoTerminal nomeNaoTerminal;
            nomeNaoTerminal = (NaoTerminal) nt.get(1);
            nome = AnalisadorSemantico.extraiIdDeNomeOuTipoPrimitivo(nomeNaoTerminal);

            //insere na tabela de simbolos
            insereSimbolo(nome, tipo, escopo, linha);
        }
        //<DEC_PARAMETROS> ::= <TIPO> [ â€˜[â€˜ â€˜]â€™ ] <NOME> { â€˜,â€™ <TIPO> [ â€˜[â€˜ â€˜]â€™ ] <NOME> }
        else if(nt.getNome().equals("DEC_PARAMETROS")){
            int indexFilho = 0;

            NaoTerminal tipoNaoTerminal = null;
            NaoTerminal nomeNaoTerminal = null;

            int indexParam = 0;

            while(indexFilho + 1 < nt.getFilhos().size() ){
                tipoNaoTerminal = (NaoTerminal) nt.get(indexFilho);

                tipo = "PARAM" + indexParam + "!" + AnalisadorSemantico.extraiIdDeTipo(tipoNaoTerminal);
                indexFilho ++;

                //tipo []
                if(nt.get(indexFilho) instanceof Terminal){
                    tipo = tipo + "[]";
                    indexFilho += 2;
                }

                //recupera o nome
                nomeNaoTerminal = (NaoTerminal) nt.get(indexFilho);
                indexFilho ++;
                nome = AnalisadorSemantico.extraiIdDeNomeOuTipoPrimitivo(nomeNaoTerminal);

                insereSimbolo(nome, tipo, escopo, linha);
                indexParam ++;

                if(indexFilho < nt.getFilhos().size() && nt.get(indexFilho) instanceof Terminal){
                    Terminal t = (Terminal) nt.get(indexFilho);
                    indexFilho ++;
                    if(t.getSimbolo().getAtributo().toString().equals(",")){

                    }
                    //indexFilho;
                }else{
                    break;
                }
            }
        }


        return;
    }

     private void insereSimbolo(String nome, String tipo, NaoTerminal escopo, int linha) {
        try {

            TabelaSimbolos.getInstance().insert(nome, tipo, escopo);

        } catch (InsertException ex) {
            ErroSemantico erro = new ErroSemantico();
            erro.setTipo(TipoErroSemantico.NOME_DUPLICADO);
            erro.setDescricao("Variável ou método '" + nome + "' duplicado");
            erro.setLinha(linha);

            analisadorSemantico.getErrosSemanticos().add(erro);
        }
    }


}
